package org.hawk.gwt.ppc.mojo;

import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.logging.Log;

/**
 * Invokes preprocessor through reflection.
 * @author alex.bereznevatiy@gmail.com
 *
 */
final class PreprocessorInvoker {
	private static final String CONFIGURATION_CLASS_NAME = "org.hawk.gwt.ppc.Configuration";
	private static final String ENTRY_POINT_METHOD_NAME = "runFromMaven";
	private static final String COMPILLER_WRAPPER_CLASS_NAME = "org.hawk.gwt.ppc.CompilerWrapper";
	
	private final String REFLECTION_PARAMETER;
	private final String PATTERN_PARAMETER;
	private final String KEEP_PREPROCESSED_SOURCES_PARAMETER;
	private final String GENERIC_SUPPORT_PARAMETER;
	private final String WAR_DIR_PARAMETER;
	private final String PROCESS_ANNOTATIONS_PARAMETER;

	private ClassLoader loader;
	
	private Log logger;
	
	private Map<String, Object> params;
	
	private List<String> modules;
	
	PreprocessorInvoker(File preprocessorJar, List<String> sources, Log logger) throws MojoExecutionException {
		this.logger = logger;
		this.loader = createLoader(preprocessorJar, sources);
		
		this.REFLECTION_PARAMETER = getParameterName("REFLECTION_PARAMETER");
		this.PATTERN_PARAMETER = getParameterName("PATTERN_PARAMETER");
		this.KEEP_PREPROCESSED_SOURCES_PARAMETER = getParameterName("KEEP_PREPROCESSED_SOURCES_PARAMETER");
		this.GENERIC_SUPPORT_PARAMETER = getParameterName("GENERIC_SUPPORT_PARAMETER");
		this.WAR_DIR_PARAMETER = getParameterName("WAR_DIR_PARAMETER");
		this.PROCESS_ANNOTATIONS_PARAMETER = getParameterName("PROCESS_ANNOTATIONS_PARAMETER");
		
		this.params = new HashMap<String, Object>();
		this.params.put(KEEP_PREPROCESSED_SOURCES_PARAMETER, Boolean.TRUE);// always true for maven
	}

	private ClassLoader createLoader(File preprocessorJar, List<String> sources)
			throws MojoExecutionException {
		try {
			List<URL> urls = new LinkedList<URL>();
			urls.add(preprocessorJar.toURI().toURL());
			for (String src : sources) {
				urls.add(new File(src).toURI().toURL());
			}
			return new URLClassLoader(urls.toArray(new URL[urls.size()]), getClass().getClassLoader());
		} catch (MalformedURLException e) {
			throw new MojoExecutionException("Internal error: " + e.getMessage());
		}
	}
	
	public void setReflectionRegex(String regex) {
		params.put(REFLECTION_PARAMETER, regex);
	}
	
	public String getReflectionRegex() {
		return (String) params.get(REFLECTION_PARAMETER);
	}
	
	public void setRegexType(String type) {
		params.put(PATTERN_PARAMETER, type);
	}
	
	public String getRegexType() {
		return (String) params.get(PATTERN_PARAMETER);
	}
	
	public void setUseGenerics(Boolean useGenerics) {
		params.put(GENERIC_SUPPORT_PARAMETER, useGenerics);
	}
	
	public Boolean getUseGenerics() {
		return (Boolean) params.get(GENERIC_SUPPORT_PARAMETER);
	}
	
	public Boolean getProcessAnnotations() {
		return (Boolean) params.get(PROCESS_ANNOTATIONS_PARAMETER);
	}
	
	public void setProcessAnnotations(Boolean val) {
		params.put(PROCESS_ANNOTATIONS_PARAMETER, val);
	}
	
	public void setOutputDirectory(File dir) {
		params.put(WAR_DIR_PARAMETER, dir.getAbsolutePath());
	}
	
	public File getOutputDirectory() {
		return new File((String) params.get(WAR_DIR_PARAMETER));
	}

	public Map<String, Object> getParams() {
		return params;
	}

	public List<String> getModules() {
		return modules;
	}

	public void setModules(List<String> modules) {
		this.modules = modules;
	}

	/**
	 * Extracts name of parameter from Configuration class.
	 * @param field name from Configuration class.
	 * @return parameter name
	 */
	private String getParameterName(String fieldName) {
		try {
			Class<?> cls = loader.loadClass(CONFIGURATION_CLASS_NAME);
			return (String)cls.getField(fieldName).get(null);
		} catch (RuntimeException ex) {
			throw ex;
		} catch (Exception ex) {
			throw new RuntimeException(ex);
		}
	}

	/**
	 * Invokes preprocessor with settings passed to this invoker.
	 * @return path to preprocessed sources
	 */
	String invoke() throws MojoExecutionException {
		Method entryPoint = null;
		try {
			Logger.get().configure(logger);
			Class<?> cls = loader.loadClass(COMPILLER_WRAPPER_CLASS_NAME);
			entryPoint = cls.getDeclaredMethod(ENTRY_POINT_METHOD_NAME, Map.class, List.class);
			entryPoint.setAccessible(true);
			return (String) entryPoint.invoke(null, getParams(), getModules());
		} catch(InvocationTargetException ex) {
			throw new MojoExecutionException("Internal error!", ex.getCause());
		} catch(Throwable ex) {
			throw new MojoExecutionException("Internal error!", ex);
		} finally {
			if (entryPoint != null) {
				entryPoint.setAccessible(false);
			}
		}
	}
}
